package cc.verywell.pureblock.msg.nfs;

import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import cc.verywell.pureblock.func.FuncIndex;
import cc.verywell.pureblock.func.FuncProvider;
import cc.verywell.pureblock.func.node.KeyShell;
import cc.verywell.pureblock.func.node.NodeBucket;
import cc.verywell.pureblock.func.node.NodeBucket.ValueShell;
import cc.verywell.pureblock.log.Pblog;

import cc.verywell.pureblock.msg.nfs.map.FSmap;
import cc.verywell.pureblock.msg.p2p.MsgCodec;
import cc.verywell.pureblock.net.AioProvider;
import cc.verywell.pureblock.net.Msg;
import cc.verywell.pureblock.net.NormalSession;
import cc.verywell.pureblock.util.StringUtil;
import cc.verywell.pureblock.util.sign.Sha;

/**
* @startuml
* a -> b : test
* @enduml
* 
* Copyright © 2019 TanYaqiu. All rights reserved.
* @author TanYaqiu 
* @address flat 601
* @date 2019年3月30日 下午8:18:03
*/
public class NfsFuncProvider implements FuncProvider,NodeBucket.NewKeyListener  {
	static public int HEAD_LENGTH = 4/*max_func_length*/+1/*msgLength*/;
	
	static final private byte SeekFile = (byte)1;
	static final private byte StoreFile = (byte)2;
	static final private byte WantFile = (byte)3;
	static final private byte MissFile = (byte)4;
	static final private byte TakeFile = (byte)5;

	final private NodeBucket nodeBucket;
	final NfsLocalStoreProvider fsmap;
	final NfsListener control;

	public NfsFuncProvider(NodeBucket nodeBucket, NfsLocalStoreProvider fsmap, NfsListener control) {
		this.nodeBucket = nodeBucket;
		this.nodeBucket.addNewKeyListener(this);
		this.fsmap = fsmap;
		this.control = control;
	}
	@Override
	public void newKey(Collection<ValueShell> keys) {
		control.fireNewNodeKey(keys);
	}
	
	/**
	 * 返回true 会暂停读包, 
	 * 可以通过 NormalSession.startRead()恢复读包;
	 * 留意TimeOut,TimeoutRunner依然会自动断开连接的;
	 * 此时可能仍然处于read锁内，但可以使用close()断开链接;
	 * @param Msg msg
	 * @param NormalSession nc
	 * @return true PauseRead;
	 */
	public boolean toDo(Msg msg,NormalSession nc) {
		byte b = ((MsgCodec)msg).getMsgCode();
		switch(b) {
		case SeekFile:{
			SeekFileMsg seekfile = (SeekFileMsg)msg;
			if(Pblog.DEBUG)Pblog.DEBUG("todo nfs.SeekFileMsg ",seekfile.fileHash==null?"null":seekfile.fileHash.size()," at ",nc);
			List<byte[]> fh = seekfile.fileHash; //LinkedList;
			if(control.fireSeekFile(nc, fh)) {
				Iterator<byte[]> it = fh.iterator();
				while(it.hasNext()) {
					if(!fsmap.contain(new KeyShell(it.next()))) {
						it.remove();
					}
				}
				if(!fh.isEmpty()) {
					nc.writeMsg(new StoreFileMsg(fh));
				}
			}
		}break;
		case StoreFile:{
			StoreFileMsg storefile = (StoreFileMsg)msg;
			if(Pblog.DEBUG)Pblog.DEBUG("todo nfs.StoreFileMsg ",storefile.fileHash==null?"null":storefile.fileHash.size()," at ",nc);
			control.fireStoreFile(nc,storefile.fileHash);
		}break;
		case WantFile:{
			WantFileMsg wantfile = (WantFileMsg)msg;
			if(control.fireWantFile(nc, wantfile.fileHash)) {
				KeyShell key = new KeyShell(wantfile.fileHash);
				if(Pblog.DEBUG)Pblog.DEBUG("todo nfs.WantFileMsg ",Integer.toHexString(key.hashCode())," at ",nc);
				byte[] bb = fsmap.get(key);
				if(bb!=null) {
					if(bb.length<=AioProvider.FILE_MAX_SIZE) {
						nc.writeMsg(new TakeFileMsg(wantfile.fileHash, bb));
					}else {
						if(Pblog.FS)Pblog.FS("err get file ,size +",bb.length-AioProvider.FILE_MAX_SIZE," hash key ",wantfile.fileHash);
						nc.writeMsg(new MissFileMsg(wantfile.fileHash));
					}
				}else {
					nc.writeMsg(new MissFileMsg(wantfile.fileHash));
				}
			}else {
				if(Pblog.DEBUG)Pblog.DEBUG("todo nfs.WantFileMsg but blocked at ",nc);
			}
		}break;
		case MissFile:{
			MissFileMsg missfile = (MissFileMsg)msg;
			KeyShell key = new KeyShell(missfile.fileHash);
			if(Pblog.DEBUG)Pblog.DEBUG("todo nfs.MissFileMsg ",Integer.toHexString(key.hashCode())," at ",nc);
			control.fireMissFile(nc,key);
		}break;
		case TakeFile:{
			TakeFileMsg takefile = (TakeFileMsg)msg;
			KeyShell key = new KeyShell(takefile.fileHash);
			if(Pblog.DEBUG)Pblog.DEBUG("todo nfs.TakeFileMsg ",Integer.toHexString(key.hashCode())," at ",nc);
			if(takefile.checkHash()) {
				if(control.fireTakeFileSuccess(nc, key)) {
					//20200206已改为NfsControl经NfsAutoStore保存//fsmap.put(key, takefile.payload);
					control.fireNewFileStored(nc, key, takefile.payload);
				}
			}else {
				control.fireTakeFileFaild(nc, key);
			}
		}break;
		default:
			throw new IllegalArgumentException("unknow "+funcName()+" code "+(0xFF & b));
		}
		return false;//msg.getFunc().toDo(msg, nc);
	}
	@Override
	public void sessionTimeout(NormalSession nc) {}


	/**
	 * 解包线程，这里出错会关掉channel;
	 * <p>此时不要用close()中断网络，因为就算中断，后续还会继续解包的，直到没数据了，或者进入一次toDo</p>
	 * <p>实在要关闭，请throw Exception</p>
	 * <p>解包或跳过均可，否则剩下的remain会进入下一轮decode</p>
	 * @param bb
	 * @return
	 */
	@Override
	public Msg decode(ByteBuffer bb, NormalSession nc) {
		//int code = 0xFF & bb.get();
		byte b = bb.get();
		switch(b) {
		case SeekFile:
			return new SeekFileMsg(bb, nc);
		case StoreFile:
			return new StoreFileMsg(bb, nc);
		case WantFile:
			return new WantFileMsg(bb, nc);
		case MissFile:
			return new MissFileMsg(bb, nc);
		case TakeFile:
			return new TakeFileMsg(bb, nc);
		default:
			throw new IllegalArgumentException("unknow "+funcName()+" code "+(0xFF & b));
		}
	}
	/**
	 * {@link FuncIndex} cacheindex可以自编解决方案，比如连数据库;
	 * <p>此时不要用close()中断网络(否则会硬解写锁)，throw也仅针对本包, 后续包会继续正常编码;</p>
	 */
	@Override
	public void encode(Msg msg, NormalSession nc, ByteBuffer bb) {
		//if(Pblog.DEBUG && msg!=null)Pblog.DEBUG("send ",funcName(),".",msg.getClass().getSimpleName()," at ",nc);
		msg.write(bb, nc);
	}
	
	/**
	 * 255bytes以内;
	 */
	@Override
	public String funcName() {
		return "NFS";
	}
	@Override
	public String toString() {
		//用于出错时找原因;
		return this.getClass().getSimpleName()+"$"+Integer.toHexString(this.hashCode())+"["+this.funcName()+"]";
	}
	
	///////////////////////////////////////////////
	
	public void newSession(NormalSession nc) {
		if(Pblog.DEBUG)Pblog.DEBUG("newSession ",this," in ",nc);
//		nfsBucket.addValue(nc.getFunc().getNode());
		control.fireNewSession(nc);
	}
	public void closeSession(NormalSession nc) {
		if(Pblog.DEBUG)Pblog.DEBUG("closeSession ",this," in ",nc);
//		nfsBucket.removeValue(nc.getFunc().getNode());
		control.fireCloseSession(nc);
	}
	public void close(){
		if(Pblog.DEBUG)Pblog.DEBUG("closeFuncProvider ",this);
		this.nodeBucket.removeNewKeyListener(this);
		control.close();
	}

	
	private int index = -1;
	@Override
	public int getIndex() {return index;}
	@Override
	public void setIndex(int index) {this.index = index;}

	/**
	 * 
	 * <p>TakeFileMsg 请求对方给我这文件;</p>
	 * <p>* 报文
	 * <br>HASH长度[2_bit] HASH[?_bits]; 
	 * <br>余下全为文件内容</p>
	 * @author TanYaqiu 
	 * Copyright © 2019 TanYaqiu. All rights reserved.
	 */
	public class TakeFileMsg implements MsgCodec{
		byte[] fileHash;
		byte[] payload;
		public TakeFileMsg(ByteBuffer bb, NormalSession nc) {
			fileHash = new byte[StringUtil.readLength(2, bb)];
			bb.get(fileHash);
			payload = new byte[bb.remaining()];
			bb.get(payload);
		}
		public TakeFileMsg(byte[] fileHash,byte[] payload) {
			this.fileHash = fileHash;
			this.payload = payload;
		}
		@Override
		public void write(ByteBuffer bb, NormalSession nc) {
			bb.put(getMsgCode());
			StringUtil.writeLength(2, fileHash.length, bb);
			bb.put(fileHash);
			bb.put(payload);
		}
		public boolean checkHash() {
			if(fileHash==null) {
				throw new NullPointerException("unknow hash==null");
			}
			////TODO 升级以支持其它hash(通过长度区分)
			Sha sha = Sha.getInstance();
			if(fileHash.length!=sha.length()) {
				throw new UnsupportHashException("unknow hash length:"+fileHash.length+" ["+Pblog.byteArray2Hex(fileHash)+"]");
			}
			byte[] hash = sha.hash(payload);
			////
			return Arrays.equals(hash, fileHash);
		}
		
		@Override
		final public FuncProvider getFunc() {
			return NfsFuncProvider.this;
		}
		@Override
		final public byte getMsgCode() {
			return TakeFile;
		}
	}

	/**
	 * <p>MissFileMsg 对方请求文件后我方发现没有文件的回应;</p>
	 * <p>* 报文
	 * <br>HASH长度[2_bit] HASH[?_bits];</p>
	 * @author TanYaqiu 
	 * Copyright © 2019 TanYaqiu. All rights reserved.
	 */
	public class MissFileMsg extends WantFileMsg{
		public MissFileMsg(byte[] fileHash) {
			super(fileHash);
		}
		public MissFileMsg(ByteBuffer bb, NormalSession nc) {
			super(bb, nc);
		}
		@Override
		public byte getMsgCode() {
			return MissFile;
		}
	}

	
	/**
	 * <p>WantFileMsg 请求对方给我这文件;</p>
	 * <p>* 报文
	 * <br>HASH长度[2_bit] HASH[?_bits]; 
	 * <br><del>起始位置[8_bit]</del>
	 * <br><del>获取长度[8_bit]</del></p>
	 * @author TanYaqiu 
	 * Copyright © 2019 TanYaqiu. All rights reserved.
	 */
	public class WantFileMsg implements MsgCodec{
		byte[] fileHash;
//		long fileFrom;
//		long fileLength;
		public WantFileMsg(ByteBuffer bb, NormalSession nc) {
			fileHash = new byte[StringUtil.readLength(2, bb)];
			bb.get(fileHash);
//			fileFrom = bb.getLong();
//			fileLength = bb.getLong();
		}
		public WantFileMsg(byte[] fileHash/*,long fileFrom,long fileLength*/) {
			this.fileHash = fileHash;
//			this.fileFrom = fileFrom;
//			this.fileLength = fileLength;
		}
		@Override
		public void write(ByteBuffer bb, NormalSession nc) {
			bb.put(getMsgCode());
			StringUtil.writeLength(2, fileHash.length, bb);
			bb.put(fileHash);
//			bb.putLong(fileFrom);
//			bb.putLong(fileLength);
		}
		
		@Override
		final public FuncProvider getFunc() {
			return NfsFuncProvider.this;
		}
		@Override
		public byte getMsgCode() {
			return WantFile;
		}
	}
	
	/**
	 * <p>StoredFileMsg 告诉对方我这有文件 </p>
	 * <p>队列长度[2_bit] HASH长度[2_bit] HASH[?_bits];</p>
	 * @author TanYaqiu 
	 * Copyright © 2019 TanYaqiu. All rights reserved.
	 */
	public class StoreFileMsg extends SeekFileMsg{
		public StoreFileMsg(ByteBuffer bb, NormalSession nc) {
			super(bb, nc);
		}
		public StoreFileMsg(List<byte[]> fileHash) {
			super(fileHash);
		}
		@Override
		public byte getMsgCode() {
			return StoreFile;
		}
	}
	
	/**
	 * <p>SeekFileMsg 用于告诉对方我在找文件,，</p>
	 * <p>队列长度[2_bit] HASH长度[2_bit] HASH[?_bits];</p>
	 * @author TanYaqiu 
	 * Copyright © 2019 TanYaqiu. All rights reserved.
	 */
	public class SeekFileMsg implements MsgCodec{
		List<byte[]> fileHash;
		public SeekFileMsg(ByteBuffer bb, NormalSession nc) {
			int size = StringUtil.readLength(2, bb);
			fileHash = new LinkedList<>();
			while(0<size--) {
				byte[] bs = new byte[StringUtil.readLength(2, bb)];
				bb.get(bs);
				fileHash.add(bs);
			}
		}
		public SeekFileMsg(List<byte[]> fileHash) {
			this.fileHash = fileHash;
		}
		@Override
		public void write(ByteBuffer bb, NormalSession nc) {
			bb.put(getMsgCode());
			StringUtil.writeLength(2, fileHash.size(), bb);
			for (byte[] bs : fileHash) {
				StringUtil.writeLength(2, bs.length, bb);
				bb.put(bs);
			}
		}
		
		@Override
		final public FuncProvider getFunc() {
			return NfsFuncProvider.this;
		}
		@Override
		public byte getMsgCode() {
			return SeekFile;
		}
		@Override
		public void failed(Throwable exc, NormalSession nc) {
			//失败就下一个？？
		}
	}
	public LinkedList<Msg> getSeekFileMsg(List<byte[]> seeking) {
		if(seeking==null || seeking.size()==0)return null;
		final int head_length = HEAD_LENGTH+2/*队列长度[2_bit]*/;
		final int max_float = 0x0000FFFF; 
		LinkedList<Msg> ll = new LinkedList<Msg>();
		int start = 0,size = head_length,i = 0;
		for (byte[] bs : seeking) {
			int s = 2+bs.length;
			size += s;
			if(size>AioProvider.MESSAGE_SIZE || i>=max_float) {
				Msg msg = new SeekFileMsg(seeking.subList(start, start+i));
				ll.add(msg);
				start += i;
				i = 0;
				size = head_length;
			}
			i++;
		}
		if(i>0) {
			Msg msg = new SeekFileMsg(seeking.subList(start, start+i));
			ll.add(msg);
		}
		return ll;
	}

}
